﻿using System;
using System.Collections.Generic;
using System.IO.Pipelines;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers;
using WholeSlideImageLibrary;
using WholeSlideImageLibrary.DeepZoom;

namespace WholeSlideImageViewer.Services
{
    internal class DeepZoomImageSource : IDeepZoomImageSource
    {
        private readonly WholeSlideImageReader _reader;
        private readonly ReaderWriterLockSlim _lock;

        private readonly ILogger _logger;

        private string? _id;
        private RetainableDeepZoomGenerator? _deepZoom;

        public DeepZoomImageSource(ILogger<DeepZoomImageSource> logger)
        {
            var builder = new WholeSlideImageReaderBuilder();
            builder.AddDmetrix();
            builder.AddOptimizedPyramidTiledImage();
            builder.AddDeepZoomDirectory();
            builder.AddPhilipsTiff();
            builder.AddGenericTiff();
            builder.AddOpenSlide();
            builder.AddImageSharp();

            _reader = builder.Build();
            _lock = new ReaderWriterLockSlim();

            _logger = logger;
        }

        public async Task<string?> OpenAsync(string fileName)
        {
            _logger.LogInformation("打开图片：{0}", fileName);

            // 打开图像
            var fileSource = new PhysicalFileSource(fileName);
            (ImageSourceType type, IImageIdentifier? identifier) = await _reader.IdentifyAsync(fileSource).ConfigureAwait(false);
            if (type != ImageSourceType.SingleImage)
            {
                _logger.LogWarning("无法打开图像。原因是：没有合适的解码器，或者文件包含多张图片。");
                return null;
            }
            WholeSlideImageShim? image = await _reader.OpenSingleImageAsync(fileSource).ConfigureAwait(false);
            if (image is null)
            {
                _logger.LogWarning("无法打开图像。原因是：解码器鉴定为{0}，但是打开接口没有返回图片。", identifier?.Name);
                return null;
            }

            // 打开DeepZoom
            DeepZoomProfile profile = DeepZoomProfile.Default;
            DeepZoomProfile[]? profiles = image.GetDeepZoomProfileHints().ToArray();
            if (profiles.Length != 0)
            {
                profile = profiles[0];
            }
            DeepZoomTileSource? deepZoom = await image.OpenDeepZoomAsync(profile).ConfigureAwait(false);
            if (deepZoom is null)
            {
                return null;
            }

            // 切换到新图像
            _lock.EnterWriteLock();
            try
            {
                _id = Guid.NewGuid().ToString();
                RetainableDeepZoomGenerator? oldImage = Interlocked.Exchange(ref _deepZoom, new RetainableDeepZoomGenerator(image, deepZoom));
                if (!(oldImage is null))
                {
                    await oldImage.DisposeAsync().ConfigureAwait(false);
                }
                return _id;
            }
            finally
            {
                _lock.ExitWriteLock();
            }
        }

        private RetainableDeepZoomGenerator? Acquire(string id)
        {
            _lock.EnterReadLock();
            try
            {
                if (id.Equals(_id, StringComparison.Ordinal))
                {
                    return _deepZoom;
                }
                return null;
            }
            finally
            {
                _lock.ExitReadLock();
            }
        }

        public RetainableDeepZoomGenerator? Retain(string id)
        {
            _lock.EnterReadLock();
            try
            {
                if (id.Equals(_id, StringComparison.Ordinal) && _deepZoom != null)
                {
                    _deepZoom.RetainDeepZoom();
                    return _deepZoom;
                }
                return null;
            }
            finally
            {
                _lock.ExitReadLock();
            }
        }

        public async Task<string?> GetDziContentAsync(string id)
        {
            if (id is null)
            {
                throw new ArgumentNullException(nameof(id));
            }

            RetainableDeepZoomGenerator? retainable = Acquire(id);
            if (retainable is null)
            {
                return null;
            }
            DeepZoomTileSource? dz = retainable.RetainDeepZoom();
            if (dz is null)
            {
                return null;
            }
            try
            {
                return dz.GetDefinitionFile();
            }
            finally
            {
                await retainable.ReleaseAsync().ConfigureAwait(false);
            }
        }

        public async Task<IEnumerable<KeyValuePair<string, string>>?> GetTextPropertiesAsync(string id)
        {
            if (id is null)
            {
                throw new ArgumentNullException(nameof(id));
            }

            RetainableDeepZoomGenerator? retainable = Acquire(id);
            if (retainable is null)
            {
                return null;
            }
            WholeSlideImageShim? image = retainable.RetainImage();
            if (image is null)
            {
                return null;
            }
            try
            {
                IEnumerable<string>? names = await image.GetAllTextPropertiesAsync();
                var list = new List<KeyValuePair<string, string>>();
                foreach (string? name in names)
                {
                    list.Add(new KeyValuePair<string, string>(name, await image.ReadTextPropertyAsync(name)));
                }
                return list;
            }
            finally
            {
                await retainable.ReleaseAsync().ConfigureAwait(false);
            }
        }

        public async Task<IEnumerable<string>?> GetAssociatedImageNamesAsync(string id)
        {
            if (id is null)
            {
                throw new ArgumentNullException(nameof(id));
            }

            RetainableDeepZoomGenerator? retainable = Acquire(id);
            if (retainable is null)
            {
                return null;
            }
            WholeSlideImageShim? image = retainable.RetainImage();
            if (image is null)
            {
                return null;
            }
            try
            {
                return await image.GetAllAssociatedImagesAsync().ConfigureAwait(false);
            }
            finally
            {
                await retainable.ReleaseAsync().ConfigureAwait(false);
            }
        }

        public async Task SendTileAsync(string id, int layer, int col, int row, string format, HttpResponse response)
        {
            if (id is null)
            {
                throw new ArgumentNullException(nameof(id));
            }

            RetainableDeepZoomGenerator? retainable = Acquire(id);
            if (retainable is null)
            {
                response.StatusCode = StatusCodes.Status404NotFound;
                return;
            }
            DeepZoomTileSource? dz = retainable.RetainDeepZoom();
            if (dz is null)
            {
                response.StatusCode = StatusCodes.Status404NotFound;
                return;
            }
            try
            {
                if (!dz.CheckTileExists(layer, col, row))
                {
                    response.StatusCode = StatusCodes.Status404NotFound;
                    return;
                }

                switch (dz.TileFileFormat)
                {
                    case EncodedImageFileFormat.Bmp:
                        if (!"bmp".Equals(format, StringComparison.OrdinalIgnoreCase))
                        {
                            response.StatusCode = StatusCodes.Status404NotFound;
                            return;
                        }
                        response.ContentType = "application/x-bmp";
                        break;
                    case EncodedImageFileFormat.Jpeg:
                        if (!"jpg".Equals(format, StringComparison.OrdinalIgnoreCase) && !"jpeg".Equals(format, StringComparison.OrdinalIgnoreCase))
                        {
                            response.StatusCode = StatusCodes.Status404NotFound;
                            return;
                        }
                        response.ContentType = "image/jpeg";
                        break;
                    case EncodedImageFileFormat.Png:
                        if (!"png".Equals(format, StringComparison.OrdinalIgnoreCase))
                        {
                            response.StatusCode = StatusCodes.Status404NotFound;
                            return;
                        }
                        response.ContentType = "image/png";
                        break;
                    default:
                        response.StatusCode = StatusCodes.Status404NotFound;
                        return;
                }

                response.Headers.Remove(HeaderNames.CacheControl);
                response.Headers.Add(HeaderNames.CacheControl, CacheControlHeaderValue.PrivateString);

                PipeWriter pipeWriter = response.BodyWriter;
                await dz.WriteToBufferAsync(layer, col, row, pipeWriter, response.HttpContext.RequestAborted).ConfigureAwait(false);
                await pipeWriter.FlushAsync(response.HttpContext.RequestAborted).ConfigureAwait(false);
            }
            finally
            {
                await retainable.ReleaseAsync().ConfigureAwait(false);
            }
        }

        public async Task SendAssociatedImageJpegAsync(string id, string name, HttpResponse response)
        {
            if (id is null)
            {
                throw new ArgumentNullException(nameof(id));
            }
            if (name is null)
            {
                throw new ArgumentNullException(nameof(name));
            }

            RetainableDeepZoomGenerator? retainable = Acquire(id);
            if (retainable is null)
            {
                response.StatusCode = StatusCodes.Status404NotFound;
                return;
            }
            WholeSlideImageShim? image = retainable.RetainImage();
            if (image is null)
            {
                response.StatusCode = StatusCodes.Status404NotFound;
                return;
            }
            try
            {
                response.Headers.Remove(HeaderNames.CacheControl);
                response.Headers.Add(HeaderNames.CacheControl, CacheControlHeaderValue.PrivateString);

                PipeWriter pipeWriter = response.BodyWriter;
                await image.EncodeAssociatedImageAsync(name, EncodedImageFileFormat.Jpeg, pipeWriter, response.HttpContext.RequestAborted).ConfigureAwait(false);
                await pipeWriter.FlushAsync(response.HttpContext.RequestAborted).ConfigureAwait(false);
            }
            finally
            {
                await retainable.ReleaseAsync().ConfigureAwait(false);
            }
        }
    }
}
